package com.xiaoyu.lanling.feature.chat.data

import `in`.srain.cube.concurrent.AppThreads
import `in`.srain.cube.concurrent.ThreadPoolStatistics
import `in`.srain.cube.util.CLog
import `in`.srain.cube.util.general.Reversed
import com.xiaoyu.base.data.UserInfoDataProvider
import com.xiaoyu.base.utils.ListUtil
import com.xiaoyu.im.ChatToken
import com.xiaoyu.im.datamodel.AppMessage
import com.xiaoyu.im.provider.AbstractMessageDataProvider
import com.xiaoyu.im.provider.ConversationManager
import com.xiaoyu.im.provider.MessageListPaginator
import com.xiaoyu.lanling.event.chat.MessageListUpdateEvent
import com.xiaoyu.lanling.feature.chat.model.message.*
import java.util.*
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock

class MessageListData private constructor() {

    private val messageItemList: MutableList<ChatMessageBaseItem> = ArrayList()

    private val mPagingExecutor: ThreadPoolExecutor
    private val mAscendComparator: AscendComparator
    private val mReadLock: Lock
    private val mWriteLock: Lock

    private var mMessageListPaginator: MessageListPaginator? = null

    init {
        val readWriteLock: ReadWriteLock = ReentrantReadWriteLock()
        mReadLock = readWriteLock.readLock()
        mWriteLock = readWriteLock.writeLock()
        mPagingExecutor = AppThreads.createThreadPool("cp-paging-executor-pool-", 0, 1)
        mAscendComparator = AscendComparator()
    }

    fun init(messageDataProvider: AbstractMessageDataProvider) {
        mMessageListPaginator = MessageListPaginator(messageDataProvider)
    }

    fun loadFirstMessageList(chatId: String) {
        resetList()
        loadMessageItemList(chatId, true)
    }

    fun loadNextMessageList(chatId: String) {
        loadMessageItemList(chatId, false)
    }

    private fun loadMessageItemList(chatId: String, firstLoad: Boolean) {
        mPagingExecutor.execute {
            mMessageListPaginator?.loadMessageList(chatId, firstLoad)?.let {
                loadMessageList(it, firstLoad, addTime = true, isFromSearch = false, messageIndex = -1)
            }
        }
        ThreadPoolStatistics.onTaskAdd(mPagingExecutor, "cp-paging-executor-pool-")
    }

    fun loadFirstMessageListByMessageId(chatId: String?, messageId: String) {
        resetList()
        mPagingExecutor.execute {
            val wrapper = mMessageListPaginator?.loadMessageListWithWrapper(chatId, messageId)
                    ?: return@execute
            loadMessageListWithSearchMessageId(wrapper.messages, newLoad = true, addTime = true, isFromSearch = true, messageIndex = wrapper.messageIndex, id = messageId)
        }
        ThreadPoolStatistics.onTaskAdd(mPagingExecutor, "cp-paging-executor-pool-")
    }

    fun onMessageRemove(message: AppMessage) {
        if (!isCurrentOpenConversation(message.chatToken)) {
            return
        }
        mWriteLock.lock()
        try {
            for (i in messageItemList.indices) {
                val item = messageItemList[i]
                if (item.message != message) {
                    continue
                }
                if (messageItemList[i] is ChatMessageTimeItem) {
                    messageItemList.removeAt(i)
                }
                messageItemList.removeAt(i)
                if (!ListUtil.isHasIndex(messageItemList, i) || !ListUtil.isHasIndex(messageItemList, i - 1)) {
                    break
                }
                val preItem = messageItemList[i - 1]
                val curItem = messageItemList[i]
                if (isShouldAddTime(curItem, preItem)) {
                    messageItemList.add(i, ChatMessageTimeItem(curItem.message))
                }
                break
            }
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
    }

    fun onNewMessageLoad(message: AppMessage) {
        if (!isCurrentOpenConversation(message.chatToken)) {
            return
        }
        if (!canShowToList(message)) {
            return
        }
        val item = MessageFactory.create(message)
        if (!isNotContains(item)) {
            return
        }
        mWriteLock.lock()
        try {
            CLog.d(TAG, "new message load: %s", message.payload)
            if (isShouldAddTime(item, ListUtil.getLast(messageItemList)) && message.getDisplayTime() > 0) {
                messageItemList.add(ChatMessageTimeItem(message))
            }
            messageItemList.add(item)
            Collections.sort(messageItemList, mAscendComparator)
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(true, false, -1)
    }

    fun onNewMessageLoadWithoutTokenCheck(message: AppMessage) {
        if (!canShowToList(message)) {
            return
        }
        val item: ChatMessageBaseItem = MessageFactory.create(message)
        if (!isNotContains(item)) {
            return
        }
        mWriteLock.lock()
        try {
            CLog.d(TAG, "new message load, without token: %s", message.payload)
            if (isShouldAddTime(ListUtil.getFirst(messageItemList), item) && message.getDisplayTime() > 0) {
                messageItemList.add(ListUtil.getFirst(messageItemList))
            }
            messageItemList.add(item)
            Collections.sort(messageItemList, mAscendComparator)
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
    }

    fun onMessageReplace(oldMessage: AppMessage, newMessage: AppMessage) {
        if (!isCurrentOpenConversation(oldMessage.chatToken) || !isCurrentOpenConversation(newMessage.chatToken)) {
            return
        }
        mWriteLock.lock()
        try {
            for (i in messageItemList.indices) {
                val curItem = messageItemList[i]
                if (curItem.message != oldMessage) {
                    continue
                }
                if (curItem is ChatMessageTimeItem) {
                    messageItemList[i] = ChatMessageTimeItem(newMessage)
                } else {
                    messageItemList[i] = MessageFactory.create(newMessage)
                }
            }
            Collections.sort(messageItemList, mAscendComparator)
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(false, false, -1)
    }

    fun onMessageUpdate(message: AppMessage) {
        if (!isCurrentOpenConversation(message.chatToken)) {
            return
        }
        mWriteLock.lock()
        try {
            for (i in messageItemList.indices) {
                val curItem = messageItemList[i]
                if (curItem !is ChatMessageTimeItem && curItem.message == message) {
                    messageItemList[i] = MessageFactory.create(message)
                    Collections.sort(messageItemList, mAscendComparator)
                    break
                }
            }
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(false, false, -1)
    }

    private fun loadMessageList(messages: List<AppMessage>, newLoad: Boolean, addTime: Boolean, isFromSearch: Boolean, messageIndex: Int) {
        var messages: List<AppMessage>? = messages
        val senderUids = ListUtil.convertToSet(messages) { message: AppMessage -> message.senderUid }
        UserInfoDataProvider.getInstance().ensureUser(senderUids)
        mWriteLock.lock()
        try {
            messages = ListUtil.filterList(messages) { message: AppMessage -> canShowToList(message) }
            val items = ListUtil.convertToList(messages, MessageFactory::create) { item: ChatMessageBaseItem -> isNotContains(item) }
            messageItemList.addAll(if (addTime) mergeTimeItemList(items) else items)
            Collections.sort(messageItemList, mAscendComparator)
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(newLoad, isFromSearch, messageIndex)
    }

    private fun loadMessageListWithSearchMessageId(messages: List<AppMessage>, newLoad: Boolean, addTime: Boolean, isFromSearch: Boolean, messageIndex: Int, id: String) {
        var messages: List<AppMessage> = messages
        val senderUids = ListUtil.convertToSet(messages) { message: AppMessage -> message.senderUid }
        UserInfoDataProvider.getInstance().ensureUser(senderUids)
        mWriteLock.lock()
        try {
            messages = ListUtil.filterList(messages) { message: AppMessage -> canShowToList(message) }
            val items = ListUtil.convertToList(messages, MessageFactory::create) { item: ChatMessageBaseItem -> isNotContains(item) }
            messageItemList.addAll(if (addTime) mergeTimeItemList(items) else items)
            Collections.sort(messageItemList, mAscendComparator)
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdateWithSearchMessageId(newLoad, isFromSearch, messageIndex, id)
    }

    fun getConversationImageList(): ArrayList<String> {
        val images = ArrayList<String>()
        for (item in copyList()) {
            if (item is ChatMessageReceiveImageItem) {
                images.add(item.imageUrl)
            } else if (item is ChatMessageSendImageItem) {
                images.add(item.imageDisplayUrl)
            }
        }
        return images
    }

    private fun canShowToList(message: AppMessage): Boolean {
        val messageType = message.payload.messageType
        return messageType != MessageType.TYPE_02_SERVER_PUSH
    }

    private fun copyList(): List<ChatMessageBaseItem> {
        mReadLock.lock()
        return try {
            ListUtil.filterList(messageItemList) { chatMessageBaseItem: ChatMessageBaseItem -> !chatMessageBaseItem.message.invisible }
        } catch (t: Throwable) {
            t.printStackTrace()
            ArrayList()
        } finally {
            mReadLock.unlock()
        }
    }

    private fun resetList() {
        mWriteLock.lock()
        try {
            messageItemList.clear()
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
    }

    fun clearMessageList(chatId: String, broadCast: Boolean = true) {
        if (!isCurrentOpenConversation(chatId)) {
            return
        }
        resetList()
        mMessageListPaginator?.reset()
        if (broadCast) {
            notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
        }
    }

    fun clearAllMessageList() {
        resetList()
        mMessageListPaginator?.reset()
        notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
    }

    fun setReadReceipt(chatId: String, time: Long) {
        if (!isCurrentOpenConversation(chatId)) {
            return
        }
        mWriteLock.lock()
        try {
            for (item in messageItemList) {
                if (item.message.time <= time) {
                    item.message.setReadReceipt()
                }
            }
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
        notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
    }

    fun setReadNewTip(message: AppMessage) {
        if (!isCurrentOpenConversation(message.chatToken)) {
            return
        }
        mWriteLock.lock()
        message.readNewTip()
        try {
            val wrapper = findMessageWrapper(message)
            wrapper?.let {
                messageItemList[it.position] = MessageFactory.create(message)
                notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
            }
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
    }

    fun onReceiveAudioMessagePlayComplete(message: AppMessage) {
        if (!isCurrentOpenConversation(message.chatToken)) {
            return
        }
        mWriteLock.lock()
        try {
            val wrapper = findMessageWrapper(message) ?: return
            val nextPosition = wrapper.position + 1
            for (i in nextPosition until messageItemList.size) {
                val nextItem: ChatMessageBaseItem = messageItemList[i]
                if (nextItem is ChatMessageReceiveAudioItem) {
                    val nextAudioItem = nextItem
                    if (nextAudioItem.newTip) {
                        nextAudioItem.mAutoPlay = true
                    }
                    messageItemList[i] = nextAudioItem
                    notifyMessageListUpdate(newLoad = false, isFromSearch = false, messageIndex = -1)
                    break
                }
            }
        } catch (t: Throwable) {
            t.printStackTrace()
        } finally {
            mWriteLock.unlock()
        }
    }

    private fun findMessageWrapper(message: AppMessage): MessageItemPositionWrapper? {
        for (i in messageItemList.indices) {
            val curItem = messageItemList[i]
            if (curItem !is ChatMessageTimeItem && curItem.message == message) {
                return MessageItemPositionWrapper(i, curItem)
            }
        }
        return null
    }

    private fun notifyMessageListUpdate(newLoad: Boolean, isFromSearch: Boolean, messageIndex: Int) {
        val copyList = copyList()
        val chatToken = ConversationManager.getInstance().currentChatToken
        MessageListUpdateEvent(chatToken, copyList, newLoad, isFromSearch, messageIndex).post()
    }

    private fun notifyMessageListUpdateWithSearchMessageId(newLoad: Boolean, isFromSearch: Boolean, messageIndex: Int, id: String) {
        val copyList = copyList()
        val chatToken = ConversationManager.getInstance().currentChatToken
        MessageListUpdateEvent(chatToken, copyList, newLoad, isFromSearch, messageIndex, id).post()
    }

    private fun isCurrentOpenConversation(chatToken: ChatToken): Boolean {
        return ConversationManager.getInstance().isCurrentOpenConversation(chatToken)
    }

    private fun isCurrentOpenConversation(chatId: String): Boolean {
        return ConversationManager.getInstance().isCurrentOpenConversation(chatId)
    }

    private fun isNotContains(item: ChatMessageBaseItem): Boolean {
        mReadLock.lock()
        return try {
            !messageItemList.contains(item)
        } finally {
            mReadLock.unlock()
        }
    }

    private class AscendComparator : Comparator<ChatMessageBaseItem> {
        override fun compare(o1: ChatMessageBaseItem, o2: ChatMessageBaseItem): Int {
            var result = java.lang.Long.compare(o1.message.getDisplayTime(), o2.message.getDisplayTime())
            if (result != 0) {
                return result
            }
            result = o1.message.id.compareTo(o2.message.id)
            if (result != 0) {
                return result
            }
            if (o2 is ChatMessageTimeItem && o1 !is ChatMessageTimeItem) {
                return 1
            }
            return if (o1 is ChatMessageTimeItem && o2 !is ChatMessageTimeItem) {
                -1
            } else 0
        }
    }

    private class MessageItemPositionWrapper(val position: Int, val item: ChatMessageBaseItem)

    companion object {
        private val TAG = MessageListData::class.java.simpleName
        private const val SHOW_TIME_INTERVAL = 2 * 60 * 1000
        val instance = MessageListData()

        private fun mergeTimeItemList(items: List<ChatMessageBaseItem>): List<ChatMessageBaseItem> {
            val result: MutableList<ChatMessageBaseItem> = ArrayList()
            var preItem: ChatMessageBaseItem? = null
            for (item in Reversed.reversed(items)) {
                if (isShouldAddTime(item, preItem) && item.message.getDisplayTime() > 0) {
                    result.add(ChatMessageTimeItem(item.message))
                }
                result.add(item)
                preItem = item
            }
            return result
        }

        private fun isShouldAddTime(item: ChatMessageBaseItem?, preItem: ChatMessageBaseItem?): Boolean {
            if (preItem == null) {
                return true
            }
            if (item == null) {
                return false
            }
            return if (item is ChatMessageTimeItem || preItem is ChatMessageTimeItem) {
                false
            } else item.message.getDisplayTime() - preItem.message.getDisplayTime() > SHOW_TIME_INTERVAL
        }
    }
}